title: Promise 对象
date: 2018.9.9
tags:
2018.9.9 星期日 23:51
1. Promise 的含义
2. 基本用法
3. Promise.prototype.then()
4. Promise.prototype.catch()
5. Promise.prototype.finally()
6. Promise.all()
7. Promise.race()
8. Promise.resolve()
9. Promise.reject()
10. 应用
11. Promise.try()
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。
优缺点:…
如果某些事件不断地反复发生,一般来说,使用 Stream 模式是比部署Promise更好的选择。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
then方法可以接受两个回调函数作为参数。
第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
Promise 新建后就会立即执行。
resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例
注意,调用resolve或reject并不会终结 Promise 的参数函数的执行。
一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
如果 Promise 状态已经变成resolved,再抛出错误是无效的。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
上面代码中,一共有三个 Promise 对象:一个由getJSON产生,两个由then产生。它们之中任何一个抛出的错误,都会被最后一个catch捕获。
一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。
catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。
此时,要是then方法里面报错,就与前面的catch无关了。
catch方法之中,还能再抛出错误。
该方法是 ES2018 引入标准的。
finally本质上是then方法的特例。
Promise.all方法接受一个数组作为参数,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p的状态由p1、p2、p3决定,分成两种情况。
注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
下面是一个例子,如果指定时间内没有获得结果,就将 Promise 的状态变为reject,否则变为resolve。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。
Promise.resolve方法的参数分成四种情况。
(1)参数是一个 Promise 实例
(2)参数是一个thenable对象
(3)参数不是具有then方法的对象,或根本就不是对象
(4)不带有任何参数
需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
上面代码中,Promise.reject方法的参数是一个thenable对象,执行以后,后面catch方法的参数不是reject抛出的“出错了”这个字符串,而是thenable对象。
提案:chrome也没有通过
下面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
回答是可以的,并且还有两种写法。第一种写法是用async函数来写。
第二种写法是使用new Promise()。`
javascriptconst f = () => console.log(‘now’);
Promise.try(f);
console.log(‘next’);
// now
// next
事实上,Promise.try存在已久,Promise 库Bluebird、Q和when,早就提供了这个方法。
由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。
但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。
事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。
```javascript
Promise.try(database.users.get({id: userId}))
.then(...)
.catch(...)
00:30